/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// r_skin.cpp - skin definition parsing and loading
//


#include "r_local.h"


#define SKINS_HASH_SIZE					(MAX_SKINS >> 2)

static skin_t *			r_skinsHashTable[SKINS_HASH_SIZE];
static skin_t *			r_skins[MAX_SKINS];
static int				r_numSkins;


/*
 ==============================================================================

 SKIN PARSING & LOADING

 ==============================================================================
*/


/*
 ==================
 R_LoadSkin
 ==================
*/
static skin_t *R_LoadSkin (const char *name, int numMappings, const skinMapping_t *mappings){

	skin_t	*skin;
	uint	hashKey;

	// See if already loaded
	hashKey = Str_HashKey(name, SKINS_HASH_SIZE, false);

	for (skin = r_skinsHashTable[hashKey]; skin; skin = skin->nextHash){
		if (!Str_ICompare(skin->name, name))
			break;
	}

	// If the skin already exists, replace it
	if (skin){
		Mem_Free(skin->mappings);

		skin->numMappings = numMappings;
		skin->mappings = (skinMapping_t *)Mem_DupData(mappings, numMappings * sizeof(skinMapping_t), TAG_RENDERER);

		return skin;
	}

	// Add a new skin
	if (r_numSkins == MAX_SKINS)
		Com_Error(false, "R_LoadSkin: MAX_SKINS hit");

	r_skins[r_numSkins++] = skin = (skin_t *)Mem_Alloc(sizeof(skin_t), TAG_RENDERER);

	// Fill it in
	Str_Copy(skin->name, name, sizeof(skin->name));
	skin->numMappings = numMappings;
	skin->mappings = (skinMapping_t *)Mem_DupData(mappings, numMappings * sizeof(skinMapping_t), TAG_RENDERER);

	// Add to hash table
	skin->nextHash = r_skinsHashTable[hashKey];
	r_skinsHashTable[hashKey] = skin;

	return skin;
}

/*
 ==================
 R_ParseSkin
 ==================
*/
static skin_t *R_ParseSkin (script_t *script, const char *name){

	token_t			token;
	skinMapping_t	mappings[MAX_SKIN_MAPPINGS];
	int				numMappings = 0;

	// Parse the skin
	if (!PS_ExpectTokenString(script, &token, "{", true)){
		Com_Printf(S_COLOR_YELLOW "WARNING: expected '{', found '%s' instead for skin '%s'\n", token.string, name);
		return NULL;
	}

	while (1){
		if (!PS_ReadToken(script, &token)){
			Com_Printf(S_COLOR_YELLOW "WARNING: no concluding '}' for skin '%s'\n", name);
			return NULL;	// End of script
		}

		if (!Str_Compare(token.string, "}"))
			break;			// End of skin

		// Create a new mapping
		if (numMappings == MAX_SKIN_MAPPINGS){
			Com_Printf(S_COLOR_YELLOW "WARNING: MAX_SKIN_MAPPINGS hit in skin '%s'\n", name);
			return NULL;
		}

		// Load the original material
		if (!Str_Compare(token.string, "*"))
			mappings[numMappings].from = NULL;
		else
			mappings[numMappings].from = R_FindMaterial(token.string, MT_GENERIC, SURFACEPARM_NONE);

		// Parse the material
		if (!PS_ReadToken(script, &token)){
			Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for skin '%s'\n", name);
			return NULL;
		}

		// Load the remapped material
		mappings[numMappings].to = R_FindMaterial(token.string, MT_GENERIC, SURFACEPARM_NONE);

		numMappings++;
	}

	// Load the skin
	return R_LoadSkin(name, numMappings, mappings);
}

/*
 ==================
 R_CreateSkin
 ==================
*/
static skin_t *R_CreateSkin (const char *name){

	skin_t		*skin;
	defData_t	*defData;
	script_t	*script;

	// See if there's a definition for this skin
	defData = Def_FindDef(DEF_SKIN, name);
	if (!defData)
		return NULL;

	// Load the script text
	script = PS_LoadScriptMemory(defData->sourceFile, defData->textString, defData->textLength, defData->sourceLine);
	if (!script)
		return NULL;

	PS_SetScriptFlags(script, SF_NOWARNINGS | SF_NOERRORS | SF_ALLOWPATHNAMES);

	// Parse it
	skin = R_ParseSkin(script, name);

	// Free the script text
	PS_FreeScript(script);

	return skin;
}

/*
 ==================
 R_FindSkin
 ==================
*/
skin_t *R_FindSkin (const char *name){

	skin_t	*skin;
	uint	hashKey;

	// See if already loaded
	hashKey = Str_HashKey(name, SKINS_HASH_SIZE, false);

	for (skin = r_skinsHashTable[hashKey]; skin; skin = skin->nextHash){
		if (!Str_ICompare(skin->name, name))
			return skin;
	}

	// Create the skin
	return R_CreateSkin(name);
}

/*
 ==================
 R_RegisterSkin
 ==================
*/
skin_t *R_RegisterSkin (const char *name){

	return R_FindSkin(name);
}


// ============================================================================


/*
 ==================
 R_RemapMaterialBySkin
 ==================
*/
material_t *R_RemapMaterialBySkin (material_t *material, skin_t *skin){

	skinMapping_t	*mapping;
	int				i;

	if (!skin)
		return material;

	for (i = 0; i < skin->numMappings; i++){
		mapping = &skin->mappings[i];

		// NULL == wildcard match
		if (!mapping->from || mapping->from == material)
			return mapping->to;
	}

	// Didn't find a match or wildcard, so stay the same
	return material;
}


/*
 ==============================================================================

 CONSOLE COMMANDS

 ==============================================================================
*/


/*
 ==================
 R_ListSkins_f
 ==================
*/
static void R_ListSkins_f (){

	skin_t	*skin;
	int		i;

	Com_Printf("\n");
	Com_Printf("      maps -name-----------\n");

	for (i = 0; i < r_numSkins; i++){
		skin = r_skins[i];

		Com_Printf("%4i: ", i);

		Com_Printf("%4i ", skin->numMappings);

		Com_Printf("%s\n", skin->name);
	}

	Com_Printf("---------------------------\n");
	Com_Printf("%i total skins\n", r_numSkins);
	Com_Printf("\n");
}

/*
 ==================
 R_ReloadSkin_f
 ==================
*/
static void R_ReloadSkin_f (){

	skin_t		*skin;
	const char	*name;
	uint		hashKey;

	if (Cmd_Argc() != 2){
		Com_Printf("Usage: reloadSkin <name>\n");
		return;
	}

	name = Cmd_Argv(1);

	// See if already loaded
	hashKey = Str_HashKey(name, SKINS_HASH_SIZE, false);

	for (skin = r_skinsHashTable[hashKey]; skin; skin = skin->nextHash){
		if (!Str_ICompare(skin->name, name))
			break;
	}

	if (!skin){
		Com_Printf("Couldn't find skin '%s'\n", name);
		return;
	}

	Com_Printf("Reloading skin '%s'\n", name);

	// Reload all the skin definitions
	Def_LoadDefs(DEF_SKIN, false);

	// Create the new skin
	R_CreateSkin(name);
}


/*
 ==============================================================================

 INITIALIZATION & SHUTDOWN

 ==============================================================================
*/


/*
 ==================
 R_InitSkins
 ==================
*/
void R_InitSkins (){

	Com_Printf("Initializing Skins\n");

	// Add commands
	Cmd_AddCommand("listSkins", R_ListSkins_f, "Lists loaded skins", NULL);
	Cmd_AddCommand("reloadSkin", R_ReloadSkin_f, "Reloads a skin", Cmd_ArgCompletion_SkinName);

	// Register the skin definition type
	Def_RegisterType(DEF_SKIN, "skin", "skins", ".skin", "listSkinDefs", "printSkinDef");

	// Load all the skin definitions
	Def_LoadDefs(DEF_SKIN, true);
}

/*
 ==================
 R_ShutdownSkins
 ==================
*/
void R_ShutdownSkins (){

	// Remove commands
	Cmd_RemoveCommand("listSkins");
	Cmd_RemoveCommand("reloadSkin");

	// Purge all the skin definitions
	Def_PurgeDefs(DEF_SKIN);

	// Clear skin list
	Mem_Fill(r_skinsHashTable, 0, sizeof(r_skinsHashTable));
	Mem_Fill(r_skins, 0, sizeof(r_skins));

	r_numSkins = 0;
}